﻿using System;
using System.Collections.Generic;
using IrrlichtLime;
using IrrlichtLime.Core;
using Timed;
using IrrlichtLime.Scene;
using IrrlichtLime.Video;
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Iso.Common
{
    public static class Utility
    {

        public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
        {
            if (val.CompareTo(min) < 0) return min;
            else if (val.CompareTo(max) > 0) return max;
            else return val;
        }

        public static int Wrap(this int val, int min, int max)
        {
            int X = val - (val - min) / (max - min) * (max - min);
            if (X < 0)
                X = X + max - min;
            return X;
        }

        public static float Wrap(this float val, float min, float max)
        {
            float X = val - (val - min) / (max - min) * (max - min);
            if (X < 0)
                X = X + max - min;
            return X;
        }

        public static double AngleBetweenToPoints(this Vector3Df p1, Vector3Df p2)
        {
            return Math.Acos((p2.X - p1.X) / p1.GetDistanceFrom(p2));
        }

        public static double ToRadians(this float val)
        {
            return Math.PI * val / 180.0;
        }

        public static float ToDegrees(this double val)
        {
            return (float)(val * (180.0 / Math.PI));
        }

        public static int Lerp(this int value1, int value2, float position)
        {
            return (int)(value1 + (value2 - value1) * position);
        }

        public static float Lerp(this float value1, float value2, float position)
        {
            return value1 + (value2 - value1) * position;
        }

        public static Vector3Df Lerp(this Vector3Df value1, Vector3Df value2, float position)
        {
            return new Vector3Df(Lerp(value1.X, value2.X, position), Lerp(value1.Y, value2.Y, position), Lerp(value1.Z, value2.Z, position));
        }

        public static IrrlichtLime.Video.Color Lerp(this IrrlichtLime.Video.Color value1, IrrlichtLime.Video.Color value2, float position)
        {
            return new IrrlichtLime.Video.Color(Lerp(value1.Red, value2.Red, position), Lerp(value1.Green, value2.Green, position),
                Lerp(value1.Blue, value2.Blue, position), Lerp(value1.Alpha, value2.Alpha, position));
        }

        public static Colorf Lerp(this Colorf value1, Colorf value2, float position)
        {
            return new Colorf(Lerp(value1.Red, value2.Red, position), Lerp(value1.Green, value2.Green, position),
                Lerp(value1.Blue, value2.Blue, position), Lerp(value1.Alpha, value2.Alpha, position));
        }

        public static Vector2Di ScreenPercent(this float percentX, float percentY)
        {
            float pX = percentX / 100, pY = percentY / 100;
            return new Vector2Di((int)(Engine.device.VideoDriver.ViewPort.Width * pX),
                (int)(Engine.device.VideoDriver.ViewPort.Height * pY));
        }

        public static Vector2Df PointToScreen(this Vector3Df position)
        {
            Dimension2Di dim = Engine.device.VideoDriver.ScreenSize;
            dim.Width /= 2;
            dim.Height /= 2;

            float[] transformedPos = new float[4];

            Matrix trans = new Matrix();
            trans *= Engine.device.SceneManager.ActiveCamera.ProjectionMatrix;
            trans *= Engine.device.SceneManager.ActiveCamera.ViewMatrix;

            transformedPos[0] = position.X;
            transformedPos[1] = position.Y;
            transformedPos[2] = position.Z;
            transformedPos[3] = 1.0f;

            trans.MultiplyWith1x4Matrix(transformedPos);

            float zDiv = transformedPos[3] == 0.0f ? 1.0f :
               (1.0f / transformedPos[3]);

            return new Vector2Df(dim.Width * transformedPos[0] * zDiv + dim.Width, dim.Height - (dim.Height * (transformedPos[1] * zDiv)));
        }

        public static Vector2Di PointToScreenI(this Vector3Df position)
        {
            Dimension2Di dim = Engine.device.VideoDriver.ScreenSize;
            dim.Width /= 2;
            dim.Height /= 2;

            float[] transformedPos = new float[4];

            Matrix trans = new Matrix();
            trans *= Engine.device.SceneManager.ActiveCamera.ProjectionMatrix;
            trans *= Engine.device.SceneManager.ActiveCamera.ViewMatrix;

            transformedPos[0] = position.X;
            transformedPos[1] = position.Y;
            transformedPos[2] = position.Z;
            transformedPos[3] = 1.0f;

            trans.MultiplyWith1x4Matrix(transformedPos);

            float zDiv = transformedPos[3] == 0.0f ? 1.0f :
               (1.0f / transformedPos[3]);

            return new Vector2Di((int)(dim.Width * transformedPos[0] * zDiv + dim.Width), (int)(dim.Height - (dim.Height * (transformedPos[1] * zDiv))));
        }

        public static void DrawNinePatch(this Recti rect)
        {
            // CORNERS
            Engine.device.VideoDriver.Draw2DImage(Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch0.png"), new Recti(rect.UpperLeftCorner, new Dimension2Di(32, 32)), new Recti(0, 0, 32, 32), null, null, true); // UPPER LEFT
            Engine.device.VideoDriver.Draw2DImage(Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch0.png"), new Recti(rect.LowerRightCorner - 32, new Dimension2Di(32, 32)), new Recti(32, 32, 64, 64), null, null, true); // LOWER RIGHT
            Engine.device.VideoDriver.Draw2DImage(Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch0.png"), new Recti(rect.UpperLeftCorner.X, rect.LowerRightCorner.Y - 32, rect.UpperLeftCorner.X + 32, rect.LowerRightCorner.Y), new Recti(0, 32, 32, 64), null, null, true); // LOWER LEFT
            Engine.device.VideoDriver.Draw2DImage(Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch0.png"), new Recti(rect.LowerRightCorner.X - 32, rect.UpperLeftCorner.Y, rect.LowerRightCorner.X, rect.UpperLeftCorner.Y + 32), new Recti(32, 0, 64, 32), null, null, true); // UPPER RIGHT

            // BORDERS
            Engine.device.VideoDriver.Draw2DImage(Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch1.png"), new Recti(rect.UpperLeftCorner.X, rect.UpperLeftCorner.Y + 32, rect.UpperLeftCorner.X + 32, rect.LowerRightCorner.Y - 32), new Recti(0, 0, 32, rect.Height - 64)); // LEFT
            Engine.device.VideoDriver.Draw2DImage(Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch1.png"), new Recti(rect.LowerRightCorner.X - 32, rect.UpperLeftCorner.Y + 32, rect.LowerRightCorner.X, rect.LowerRightCorner.Y - 32), new Recti(0, 0, -32, rect.Height - 64)); // RIGHT
            Engine.device.VideoDriver.Draw2DImage(Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch2.png"), new Recti(rect.UpperLeftCorner.X + 32, rect.UpperLeftCorner.Y, rect.LowerRightCorner.X - 32, rect.UpperLeftCorner.Y + 32), new Recti(0, 0, rect.Width - 64, 32)); // UPPER
            Engine.device.VideoDriver.Draw2DImage(Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch2.png"), new Recti(rect.UpperLeftCorner.X + 32, rect.LowerRightCorner.Y - 32, rect.LowerRightCorner.X - 32, rect.LowerRightCorner.Y), new Recti(0, 0, rect.Height - 64, -32)); // LOWER

            // FILL
            Engine.device.VideoDriver.Draw2DImage(Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch3.png"), new Recti(rect.UpperLeftCorner + 32, rect.LowerRightCorner - 32), new Recti(0, 0, rect.Width - 32, rect.Height - 32));
        }

        public static Texture RenderText(this string text, int width, int height, Font font, Brush brush, StringAlignment align = StringAlignment.Near)
        {
            Texture tex = null;
            Bitmap b = new Bitmap(width, height);
            Graphics g = Graphics.FromImage(b);
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            StringFormat format = new StringFormat();
            format.Alignment = align;
            g.DrawString(text, font, brush, new Rectangle(new Point(), b.Size), format);
            g.Flush();

            if (b != null)
            {
                tex = Engine.device.VideoDriver.AddTexture(new Dimension2Di(b.Width, b.Height), "text", ColorFormat.A8R8G8B8);
                BitmapData data = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
                    ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                byte[] bytes = new byte[data.Height * data.Stride];
                Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
                tex.Painter.Lock(TextureLockMode.WriteOnly);
                Marshal.Copy(bytes, 0, tex.Painter.MipMapLevelData, data.Height * data.Stride);
                tex.Painter.Unlock();
                b.UnlockBits(data);
                return tex;
            }

            b.Dispose();
            return tex;
        }

        public static Texture BitmapToTexture(this Bitmap b)
        {
            Texture tex = null;

            if (b != null)
            {
                tex = Engine.device.VideoDriver.AddTexture(new Dimension2Di(b.Width, b.Height), "bitmap", ColorFormat.A8R8G8B8);
                BitmapData data = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
                    ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                byte[] bytes = new byte[data.Height * data.Stride];
                Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
                tex.Painter.Lock(TextureLockMode.WriteOnly);
                
                Marshal.Copy(bytes, 0, tex.Painter.MipMapLevelData, data.Height * data.Stride);
                tex.Painter.Unlock();
                b.UnlockBits(data);
                return tex;
            }

            b.Dispose();
            return tex;
        }

        public static string[] GetFileLines(this string filename)
        {
            try
            {
                return File.ReadAllLines(filename);
            }
            catch (Exception e)
            {
                MessageBox.Show("Error in reading file: " + e.Message + ". Please report");
                Engine.device.Close();
            }

            return null;
        }

        public static Mesh CreateQuadMesh(this Vector3Df p1, Vector3Df p2)
        {
            VertexBuffer vbuff = VertexBuffer.Create();
            Vector3Df normal = p1.CrossProduct(p2);
            vbuff.Add(new Vertex3D(p1, normal, IrrlichtLime.Video.Color.OpaqueWhite, new Vector2Df(0, 0)));
            vbuff.Add(new Vertex3D(new Vector3Df(p1.X, p2.Y, p1.Z), normal, IrrlichtLime.Video.Color.OpaqueWhite, new Vector2Df(1, 0)));
            vbuff.Add(new Vertex3D(new Vector3Df(p2.X, p1.Y, p2.Z), normal, IrrlichtLime.Video.Color.OpaqueWhite, new Vector2Df(0, 1)));
            vbuff.Add(new Vertex3D(p2, normal, IrrlichtLime.Video.Color.OpaqueWhite, new Vector2Df(1, 1)));
            MeshBuffer buffer = MeshBuffer.Create(VertexType.Standard, IndexType._32Bit);
            buffer.Append(vbuff.Vertices, new uint[] { 0, 1, 2, 1, 3, 2 });
            buffer.RecalculateBoundingBox();
            vbuff.Drop();
            Mesh mesh = Mesh.Create();
            mesh.AddMeshBuffer(buffer);
            buffer.Drop();
            mesh.RecalculateBoundingBox();
            return mesh;
        }

        public static void PreloadGraphics()
        {
            Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch0.png");
            Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch1.png");
            Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch2.png");
            Engine.device.VideoDriver.GetTexture(Engine.baseData + "ui/ninepatch3.png");
        }

        public static PlayerAnimRot GetRotation(this float angle)
        {
            if (angle < 45)
                return PlayerAnimRot.N;
            else if (angle < 90)
                return PlayerAnimRot.NE;
            else if (angle < 135)
                return PlayerAnimRot.E;
            else if (angle < 180)
                return PlayerAnimRot.SE;
            else if (angle < 225)
                return PlayerAnimRot.S;
            else if (angle < 270)
                return PlayerAnimRot.SW;
            else if (angle < 315)
                return PlayerAnimRot.W;
            else if (angle < 360)
                return PlayerAnimRot.NW;
            else
                return PlayerAnimRot.N;
        }

        public static bool PointInPolygon(this Vector2Df[] points, Vector2Df point)
        {
            int i, j;
            bool c = false;
            for (i = 0, j = points.Length - 1; i < points.Length; j = i++)
            {
                if (((points[i].Y > point.Y) != (points[j].Y > point.Y)) &&
                 (point.X < (points[j].X - points[i].X) * (point.Y - points[i].Y) / (points[j].Y - points[i].Y) + points[i].X))
                    c = !c;
            }
            return c;
        }
    }
}
